// myshell.c
#include <stdio.h>
#include <stdlib.h>    // getenv
#include <assert.h>    // assert
#include <string.h>    // strlen, strtok
#include <unistd.h>    // fork, exec*, exit
#include <sys/types.h> // wait
#include <sys/wait.h>  // wait
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>

// 定义成宏是为了方便改
#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44

// 重定向
#define NONE -1
#define IN_RDIR 0
#define OUT_RDIR 1
#define APPEND_RDIR 2

char commandline[LINE_SIZE];  // 保存初始输入的字符串
char *pipecommand[ARGC_SIZE]; // 保存管道分割后的字符串
char *argv[ARGC_SIZE];        // 保存空格分割后的字符串
char pwd[LINE_SIZE];          // 用来保存当前路径
char myenv[LINE_SIZE];        // 保存自己导入的环境变量
int lastcode = 0;             // 保存上一个命令的退出码
int quit = 0;
int stdiofd[2]; // 保存标准输入输出的struct file*，初始化时存进去，最后执行完有重定向的内建命令再把0和1文件描述符改回来

int test = 0; // 测试用，1测试，输出信息，0不测试
char c;       // 测试用，用来在特定位置停顿

const char *get_username()
{
    return getenv("USER");
}
const char *get_hostname()
{
    return getenv("HOSTNAME");
}
const char *get_pwd()
{
    return getenv("PWD");
}

// 交互，互动，输入指令
int interact(char *cline, int size)
{
    // 命令行
    printf(LEFT "%s@%s %s" RIGHT "" LABLE " ", get_username(), get_hostname(), get_pwd());
    fflush(stdout);
    char *s = fgets(cline, size, stdin);
    assert(s); // assert编译时有效，运行时无效，debug模式有效，release模式无效
    (void)s;

    // "abcd\n\0"，strlen求出来的字符串长度是5，包括'\n'，'\n'的下标是4，所以要-1
    cline[strlen(cline) - 1] = '\0';

    // // 用scanf读取整行字符串的方式
    // if (scanf("%[^\n]%*c", cline) == 0)
    // {
    //     getchar();
    //     return 1;
    // }
    return 0;
}

// 字符串分割 -- 空格
int splitstring(char cline[], char *_argv[])
{
    int i = 0;
    // 字符串分割，下面两行代码，就可以对整个字符串全部做切割，并且将argv最后一个参数置为了NULL
    _argv[i++] = strtok(cline, DELIM);
    while (_argv[i++] = strtok(NULL, DELIM))
        ;

    if (test)
    {
        for (int j = 0; j < i - 1; ++j)
        {
            fprintf(stderr, "argv[%d]: %s\n", j, _argv[j]);
        }
    }
    // 接收到NULL之后，i又++了一次，所以此时参数个数是i-1
    return i - 1;
}

// 内键命令
int inner_key_command(int _argc, char *_argv[])
{
    if (_argc == 2 && strcmp(_argv[0], "cd") == 0)
    {
        chdir(_argv[1]);                   // 更改当前路径
        getcwd(pwd, LINE_SIZE);            // 获取当前路径，存到字符数组pwd中
        sprintf(getenv("PWD"), "%s", pwd); // 把pwd里的字符串刷新到环境变量PWD中
        lastcode = 0;
        return 1;
    }

    if (_argc == 2 && strcmp(_argv[0], "export") == 0)
    {
        strcpy(myenv, _argv[1]);
        putenv(myenv);
        lastcode = 0;
        return 1;
    }

    if (_argc == 2 && strcmp(_argv[0], "echo") == 0)
    {
        if (strcmp(_argv[1], "$?") == 0)
        {
            printf("%d\n", lastcode);
        }
        else if (*_argv[1] == '$')
        {
            // getenv，如果有就返回环境变量的值，如果没有就返回NULL
            char *val = getenv(_argv[1] + 1);
            if (val)
                printf("%s\n", val);
        }
        else
        {
            // 这里还要处理双引号等，就不处理了，直接打印吧
            printf("%s\n", _argv[1]);
        }
        lastcode = 0;
        return 1;
    }

    // 特殊处理：ls带颜色
    if (strcmp(_argv[0], "ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }

    return 0;
}

// 字符串分割 -- 管道
int splitpipe(char *cline)
{
    int pipeNum = 0; // 管道个数
    pipecommand[pipeNum] = cline;
    char *str = cline;
    while (*str)
    {
        if (*str == '|')
        {
            pipecommand[++pipeNum] = str + 1;
            *str = '\0';
        }
        ++str;
    }
    pipecommand[pipeNum + 1] = NULL;
    return pipeNum;
}

// 处理输入输出重定向
void redirect(char *cmd)
{
    // ls -a -l >/>>/< filename.txt，处理成：ls -a -l \0 filename.txt
    char *rdirfilename[ARGC_SIZE] = {NULL}; // 重定向的文件名
    int rdir[ARGC_SIZE] = {NONE};           // 哪种重定向
    int cnt = 0;                            // 计数，有几个重定向符号
    char *pos = cmd;
    while (*pos)
    {
        if (*pos == '>')
        {
            if (*(pos + 1) == '>')
            {
                char *prev = pos - 1;
                while (isspace(*prev))
                    *prev = '\0';
                *pos++ = '\0';
                *pos++ = '\0';
                while (isspace(*pos))
                    ++pos;
                rdirfilename[cnt] = pos;
                rdir[cnt] = APPEND_RDIR;
                ++cnt;
                continue;
            }
            else
            {
                char *prev = pos - 1;
                while (isspace(*prev))
                    *prev = '\0';
                *pos++ = '\0';
                while (isspace(*pos))
                    ++pos;
                rdirfilename[cnt] = pos;
                rdir[cnt] = OUT_RDIR;
                ++cnt;
                continue;
            }
        }
        else if (*pos == '<')
        {
            char *prev = pos - 1;
            while (isspace(*prev))
                *prev = '\0';
            *pos++ = '\0';
            while (isspace(*pos))
                ++pos;
            rdirfilename[cnt] = pos;
            rdir[cnt] = IN_RDIR;
            ++cnt;
            continue;
        }
        else
        {
            // do nothing
        }
        ++pos;
    }

    // 重定向
    int fd = 0;
    for (int i = 0; i < cnt; ++i)
    {
        if (rdir[i] == IN_RDIR)
        {
            if (test)
                fprintf(stderr, "rdir: %d, %s", rdir[i], rdirfilename[i]);
            fd = open(rdirfilename[i], O_RDONLY);
            dup2(fd, 0);
        }
        else if (rdir[i] == OUT_RDIR)
        {
            if (test)
                fprintf(stderr, "rdir: %d, %s", rdir[i], rdirfilename[i]);
            fd = open(rdirfilename[i], O_CREAT | O_WRONLY | O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if (rdir[i] == APPEND_RDIR)
        {
            if (test)
                fprintf(stderr, "rdir: %d, %s", rdir[i], rdirfilename[i]);
            fd = open(rdirfilename[i], O_CREAT | O_WRONLY | O_APPEND, 0666);
            dup2(fd, 1);
        }
        close(fd);
    }
}

// 执行命令
void ExecuteCommand(int pipe_num)
{
    // 1.根据管道数量创建匿名管道
    int pipefds[ARGC_SIZE][2];
    for (int i = 0; i < pipe_num; ++i)
    {
        if (pipe(pipefds[i]) == -1)
        {
            perror("pipe");
            exit(-1);
        }
        if (test)
            printf("pipefds[%d]: %d, %d\n", i, pipefds[i][0], pipefds[i][1]);
    }
    if (test)
        read(stdiofd[0], &c, 1);

    // 2.执行命令
    for (int i = 0; i <= pipe_num; ++i)
    {

        // 1.遍历字符串，有没有重定向符号，有就重定向
        redirect(pipecommand[i]);
        if (test)
            read(stdiofd[0], &c, 1);

        // 假设：commandline -> "ls -a -l -n\0"，拿到指令后，接下来就是执行命令，先做字符串分割
        // 2. 子串分割，解析命令行
        int argc = splitstring(pipecommand[i], argv);
        if (argc == 0)
            return;
        if (test)
            read(stdiofd[0], &c, 1);

        // 3.重定向到管道
        if (i != pipe_num)
        {
            // close(pipefds[i][0]);
            dup2(pipefds[i][1], 1);
            if (test)
                fprintf(stderr, "w->pipe%d\n", i + 1);
        }
        if (i != 0)
        {
            // close(pipefds[i-1][1]);
            dup2(pipefds[i - 1][0], 0);
            if (test)
                fprintf(stderr, "r->pipe%d\n", i);
        }
        if (test)
            read(stdiofd[0], &c, 1);

        // 4. 指令的判断，指令分内键命令和普通命令
        int n = inner_key_command(argc, argv);
        if (test)
        {
            fprintf(stderr, "n: %d\n", n);
            read(stdiofd[0], &c, 1);
        }

        // 5. 普通命令的执行--交给子进程
        if (!n)
        {
            if (test)
            {
                fprintf(stderr, "fork before\n");
                read(stdiofd[0], &c, 1);
            }
            pid_t id = fork();
            if (test)
            {
                fprintf(stderr, "fork after\n");
                read(stdiofd[0], &c, 1);
            }
            if (id < 0)
            {
                perror("fork");
                exit(-1);
            }
            else if (id == 0)
            {
                // child
                if (test)
                {
                    fprintf(stderr, "child\n");
                    read(stdiofd[0], &c, 1);
                }

                if (i > 0)
                {
                    // 把当前进程读端管道的写端描述符关掉，不然文件读不到结尾，无法退出
                    close(pipefds[i - 1][1]);
                    if (test)
                    {
                        fprintf(stderr, "close pipefds[%d][1]\n", i - 1);
                        read(stdiofd[0], &c, 1);
                    }
                }
                // for (int j = 0; j < pipe_num; ++j)
                // {
                //     close(pipefds[j][0]);
                //     close(pipefds[j][1]);
                //     if (test)
                //     {
                //         fprintf(stderr, "close pipefds: [%d][0], [%d][1]\n", j, j);
                //         read(stdiofd[0], &c, 1);
                //     }
                // }

                // execvpe(_argv[0], _argv, environ);
                execvp(argv[0], argv);
                exit(EXIT_CODE); // 只有替换失败，才会走到这里
            }
            else
            {
                if (test)
                {

                    fprintf(stderr, "father\n");
                    read(stdiofd[0], &c, 1);
                }

                if (i > 0)
                {
                    close(pipefds[i - 1][0]);
                    close(pipefds[i - 1][1]);
                }

                // 让父进程等待子进程
                int status = 0;
                pid_t rid = waitpid(id, &status, 0);
                if (rid == id)
                {
                    // 获取退出码
                    lastcode = WEXITSTATUS(status);
                    if (test)
                    {
                        fprintf(stderr, "wait success\n");
                        read(stdiofd[0], &c, 1);
                    }
                }
            }
        }
        // 下一次创建子进程前，把0和1文件描述符改回来
        dup2(stdiofd[0], 0);
        dup2(stdiofd[1], 1);
    }
}

int main()
{
    while (!quit)
    {
        // 0.初始化，把键盘和显示器的struct file*存一下
        if (!pipe(stdiofd))
        {
            if (test)
                fprintf(stderr, "stdiofd[2]: %d, %d\n", stdiofd[0], stdiofd[1]);
            dup2(0, stdiofd[0]);
            dup2(1, stdiofd[1]);
        }

        // 1. 交互问题，获取命令行
        if (interact(commandline, sizeof(commandline)))
            continue;

        // 2. 按管道分割字符串
        int num = splitpipe(commandline);

        // 3. 执行命令
        ExecuteCommand(num);

        // 4. 收尾，把0和1文件描述符改回来
        dup2(stdiofd[0], 0);
        dup2(stdiofd[1], 1);
    }
    return 0;
}
